﻿//Copyright (c) 2010 Christopher E. S. King (http://chriskingconsulting.com)
//
//Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files 
// (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, 
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do 
// so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;

namespace King.Extensions {

    public interface IStreamable<T> : IEnumerable<T>, IEnumerator<T> { 
    }

    public static partial class UniversalExtensions {

        /// <summary>
        /// Return a link list starting at this link and traversing to the next link using the next function. 
        /// The traversal ends when the default(T) is encountered.
        /// </summary>
        /// <typeparam name="T">The type of the elements in the enumeration.</typeparam>
        /// <param name="source">The node to traverse.</param>
        /// <param name="next">A lambda to return the next link in the chain.</param>
        /// <returns>An enumeration starting at the source and generating the next link using the next function 
        /// until default(T) is reached.</returns>
        public static IEnumerable<T> ToLinkList<T>(this T source, Func<T, T> next) {
            Contract.Requires(source != null);
            Contract.Requires(next != null);

            return source.ToLinkList(next, o => o);
        }

        /// <summary>
        /// Return a link list starting at this link and traversing to the next link using the next function. 
        /// The traversal ends when the default(T) is encountered.
        /// </summary>
        /// <typeparam name="T">The type of the elements in the enumeration.</typeparam>
        /// <param name="source">The node to traverse.</param>
        /// <param name="next">A lambda to return the next link in the chain.</param>
        /// <param name="projection">Project each link in the chain.</param>
        /// <returns>An enumeration starting at the source and generating the next link using the next function 
        /// until default(T) is reached.</returns>
        public static IEnumerable<TResult> ToLinkList<T, TResult>(this T source, Func<T, T> next, Func<T, TResult> projection) {
            Contract.Requires(source != null);
            Contract.Requires(projection != null);
            Contract.Requires(next != null);

            while (!object.Equals(source, default(T))) {
                yield return projection(source);
                source = next(source);
            }
        }
    }

    public static partial class ObjectExtensions {

        /// <summary>
        /// Normalize source object to an IEnumerable.  If source IEnumerable then simply return
        /// source cast to IEnumerable. If source is not IEnumerable then return an array with source as 
        /// its only element. If source is null then return an empty IEnumerable.
        /// </summary>
        /// <param name="source">The object to normalize as an IEnumerable. Can be null.</param>
        /// <returns>Returns source normalized as an IEnumerable.</returns>
        public static IEnumerable<object> ToEnumerable(this object source) {
            
            if (source == null)
                return Enumerable.Empty<object>();

            if (source is IEnumerable)
                return ((IEnumerable)source).Cast<object>();

            return new[] { source };
        }

        /// <summary>
        /// Normalize source object to an IEnumerable&lt;T&gt;. If source IEnumerable&lt;T&gt; then simply return
        /// source cast to IEnumerable&lt;T&gt;. If source is not IEnumerable&lt;T&gt; then return an array with source as 
        /// its only element. If source is null then return an empty IEnumerable&lt;T&gt;.
        /// Throw an InvalidCastException if source cannot be normalized to IEnumerable&lt;T&gt;.
        /// </summary>
        /// <param name="source">The object to normalize as an IEnumerable&lt;T&gt;. Can be null.</param>
        /// <returns>Returns source normalized as an IEnumerable&lt;T&gt;.</returns>
        public static IEnumerable<T> ToEnumerable<T>(this object source) {

            if (source == null)
                return Enumerable.Empty<T>();

            if (source is T)
                return new T[] { (T)source };

            if (source is IEnumerable<T>)
                return ((IEnumerable<T>)source);

            throw new InvalidCastException();
        }
    }

    public static partial class DictionaryExtensions {

        /// <summary>
        /// Return the value associated with a key or the default value if the key is not found. Note that the 
        /// value associated with the key may be default value.
        /// </summary>
        /// <typeparam name="TKey">The type of the key.</typeparam>
        /// <typeparam name="TValue">The type of the value.</typeparam>
        /// <param name="source">The dictionary.</param>
        /// <param name="key">The key to look for in the dictionary.</param>
        /// <returns></returns>
        public static TValue GetValueOrDefault<TKey, TValue>(this Dictionary<TKey, TValue> source, TKey key) {
            Contract.Requires(source != null);

            TValue value = default(TValue);
            source.TryGetValue(key, out value);
            return value;
        }

        /// <summary>
        /// Creates a case insensitive System.Collections.Generic.Dictionary<TKey,TValue> 
        /// from an System.Collections.Generic.IEnumerable<T>
        /// according to specified key selector and element selector functions.
        /// </summary>
        /// <typeparam name="TSource">The type of the elements of source.</typeparam>
        /// <typeparam name="TElement">The type of the value returned by elementSelector.</typeparam>
        /// <param name="source">An System.Collections.Generic.IEnumerable<T> to 
        /// create a System.Collections.Generic.Dictionary<TKey,TValue> from.</param>
        /// <param name="keySelector">A function to extract a key from each element.</param>
        /// <param name="elementSelector">A transform function to produce a result element value from each element.</param>
        /// <returns>
        /// A System.Collections.Generic.Dictionary<TKey,TValue> that contains values
        /// of type TElement selected from the input sequence.
        /// </returns>
        public static Dictionary<string, TElement> ToCaseInsensitiveDictionary<TSource, TElement>(
            this IEnumerable<TSource> source, 
            Func<TSource, string> keySelector,
            Func<TSource, TElement> elementSelector) {

            Contract.Requires(source != null);
            Contract.Requires(keySelector != null);
            Contract.Requires(elementSelector != null);
            
            return new Dictionary<string, TElement>(
                source.ToDictionary(o => keySelector(o), o => elementSelector(o)), 
                StringComparer.InvariantCultureIgnoreCase);
        }

        /// <summary>
        /// Creates a case insensitive System.Collections.Generic.Dictionary<TKey,TValue> 
        /// from an System.Collections.Generic.IEnumerable<T>
        /// according to specified key selector and element selector functions.
        /// </summary>
        /// <typeparam name="TSource">The type of the elements of source.</typeparam>
        /// <param name="source">An System.Collections.Generic.IEnumerable<T> to 
        /// create a System.Collections.Generic.Dictionary<TKey,TValue> from.</param>
        /// <param name="keySelector">A function to extract a key from each element.</param>
        /// <returns>
        /// A System.Collections.Generic.Dictionary<TKey,TValue> that contains values
        /// of type TElement selected from the input sequence.
        /// </returns>
        public static Dictionary<string, TSource> ToCaseInsensitiveDictionary<TSource>(
            this IEnumerable<TSource> source, Func<TSource, string> keySelector) {

            Contract.Requires(source != null);
            Contract.Requires(keySelector != null);

            return source.ToCaseInsensitiveDictionary(keySelector, o => o);
        }
    }

    public static partial class HashsetExtensions {

        /// <summary>
        /// Creates a System.Collections.Generic.HashSet<T> from an System.Collections.Generic.IEnumerable<T>.
        /// </summary>
        /// <typeparam name="T">The type of elements stored in the source System.Collections.Generic.IEnumerable<TSource>
        /// and the resulting System.Collections.Generic.HashSet<TElement></typeparam>
        /// <param name="source">The System.Collections.Generic.IEnumerable<T> to store into a new System.Collections.Generic.HashSet<T></param>
        /// <returns>A System.Collections.Generic.HashSet<T> that contains values of type T selected from the input sequence.</returns>
        public static HashSet<T> ToHashSet<T>(this IEnumerable<T> source) {
            return source.ToHashSet(o => o);
        }

        /// <summary>
        /// Creates a System.Collections.Generic.HashSet<TElement> from an System.Collections.Generic.IEnumerable<TSource>
        /// according to specified element selector functions.
        /// </summary>
        /// <typeparam name="TSource">The type of elements stored in the source System.Collections.Generic.IEnumerable<TSource></typeparam>
        /// <typeparam name="TElement">The type of element to be stored in the resulting System.Collections.Generic.HashSet<TElement></typeparam>
        /// <param name="source">The System.Collections.Generic.IEnumerable<TSource> to project into a new System.Collections.Generic.HashSet<TElement></param>
        /// <param name="elementSelector">A transform function to produce a result element value from each element.</param>
        /// <returns>A System.Collections.Generic.HashSet<TElement> that contains values of type TElement selected from the input sequence.</returns>
        public static HashSet<TElement> ToHashSet<TSource, TElement>(
            this IEnumerable<TSource> source, Func<TSource, TElement> elementSelector) {

            Contract.Requires(source != null);
            Contract.Requires(elementSelector != null);

            var hashSet = new HashSet<TElement>();
            foreach (var element in source)
                hashSet.Add(elementSelector(element));
            return hashSet;
        }

        /// <summary>
        /// Creates a case insensitive System.Collections.Generic.HashSet<string> 
        /// from an System.Collections.Generic.IEnumerable<string>.
        /// </summary>
        /// <param name="source">An System.Collections.Generic.IEnumerable<string> to 
        /// create a System.Collections.Generic.HashSet<string> from.</param>
        /// <returns>
        /// A System.Collections.Generic.HashSet<string> that contains values
        /// of type TElement selected from the input sequence.
        /// </returns>
        public static HashSet<string> ToCaseInsensitiveHashSet(this IEnumerable<string> source) {
            Contract.Requires(source != null);

            return source.ToCaseInsensitiveHashSet(o => o);
        }

        /// <summary>
        /// Creates a case insensitive System.Collections.Generic.HashSet<string> 
        /// from an System.Collections.Generic.IEnumerable<T>
        /// according to specified element selector functions.
        /// </summary>
        /// <typeparam name="TSource">The type of the elements of source.</typeparam>
        /// <typeparam name="TElement">The type of the value returned by elementSelector.</typeparam>
        /// <param name="source">An System.Collections.Generic.IEnumerable<T> to 
        /// create a System.Collections.Generic.HashSet<T> from.</param>
        /// <param name="keySelector">A function to extract a key from each element.</param>
        /// <param name="elementSelector">A transform function to produce a result element value from each element.</param>
        /// <returns>
        /// A System.Collections.Generic.HashSet<string> that contains values
        /// of type TElement selected from the input sequence.
        /// </returns>
        public static HashSet<string> ToCaseInsensitiveHashSet<T>(this IEnumerable<T> source, Func<T, string> elementSelector) {
            Contract.Requires(source != null);
            Contract.Requires(elementSelector != null);

            var hashSet = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase);
            foreach (var element in source)
                hashSet.Add(elementSelector(element));
            return hashSet;
        }

        /// <summary>
        /// Adds the elements of the specified collection to the System.Collections.Generic.HashSet<T>.
        /// </summary>
        /// <typeparam name="T">The type of elements stored in the hashset.</typeparam>
        /// <param name="source">The hashset to which elements will be added.</param>
        /// <param name="collection">
        /// The collection whose elements should be added to the System.Collections.Generic.HashSet<T>.
        /// The collection itself cannot be null.
        /// </param>
        public static void AddRange<T>(this HashSet<T> source, params T[] collection) {
            Contract.Requires(source != null);
            Contract.Requires(collection != null);

            source.AddRange((IEnumerable<T>)collection);
        }

        /// <summary>
        /// Adds the elements of the specified collection to the System.Collections.Generic.HashSet<T>.
        /// </summary>
        /// <typeparam name="T">The type of elements stored in the hashset.</typeparam>
        /// <param name="source">The hashset to which elements will be added.</param>
        /// <param name="collection">
        /// The collection whose elements should be added to the System.Collections.Generic.HashSet<T>.
        /// The collection itself cannot be null.
        /// </param>
        public static void AddRange<T>(this HashSet<T> source, IEnumerable<T> collection) {
            Contract.Requires(source != null);
            Contract.Requires(collection != null);

            foreach (var element in collection)
                source.Add(element);
        }

        /// <summary>
        /// Removes the elements of the specified collection to the System.Collections.Generic.HashSet<T>.
        /// </summary>
        /// <typeparam name="T">The type of elements stored in the hashset.</typeparam>
        /// <param name="source">The hashset to which elements will be removed.</param>
        /// <param name="collection">
        /// The collection whose elements should be removed to the System.Collections.Generic.HashSet<T>.
        /// The collection itself cannot be null.
        /// </param>
        public static void RemoveRange<T>(this HashSet<T> source, IEnumerable<T> range) {
            foreach (var element in range)
                source.Remove(element);
        }
    }

    public static partial class EnumerableExtensions {

        /// <summary>
        /// Verifies that two specified IEnumerales contain the same elements in the same order.
        /// If either source or target are null they are normalized to empty enumerations.
        /// </summary>
        /// <param name="source">The first IEnumerable to compare.</param>
        /// <param name="target">The second IEnumerable to compare.</param>
        /// <param name="comparer">The Comparer to use to compare elements. If null then Comparer.Default is used.</param>
        /// <returns>Return true if both enumerations contain the same elements in the same order.</returns>
        public static bool AreEqual(this IEnumerable source, IEnumerable target, IComparer comparer = null) {
            if (source == null) source = source.ToEnumerable();
            if (target == null) target = target.ToEnumerable();

            if (comparer == null)
                comparer = Comparer.Default;

            var e = target.GetEnumerator();

            foreach (var o in source) {
                if (!e.MoveNext())
                    return false;

                if (!e.Current.Equals(o))
                    return false;
            }

            return !e.MoveNext();
        }

        /// <summary>
        /// Return elements which are not default(T).
        /// </summary>
        /// <typeparam name="T">Type type of the elements in the enumeration.</typeparam>
        /// <param name="source">The enumeration.</param>
        /// <returns>Return elements which are not default(T).</returns>
        public static IEnumerable<T> WhereNotDefault<T>(this IEnumerable<T> source) {
            Contract.Requires(source != null);

            return source.Where(o => !object.Equals(default(T), o));
        }

        /// <summary>
        /// Determines whether a sequence contains no elements.
        /// </summary>
        /// <typeparam name="T">The type of the elements of source.</typeparam>
        /// <param name="source">The System.Collections.Generic.IEnumerable[T] to check for emptiness.</param>
        /// <returns>true if the source sequence contains no elements; otherwise, false.</returns>
        public static bool None<T>(this IEnumerable<T> source) {
            Contract.Requires(source != null);

            return !source.Any();
        }

        /// <summary>
        /// Determines whether a sequence contains no elements.
        /// </summary>
        /// <typeparam name="T">The type of the elements of source.</typeparam>
        /// <param name="source">The System.Collections.Generic.IEnumerable[T] to check for emptiness.</param>
        /// <param name="predicate">A predicate to test for each element.</param>
        /// <returns>true if the source sequence contains no elements; otherwise, false.</returns>
        /// <summary>
        public static bool None<T>(this IEnumerable<T> source, Func<T, bool> predicate) {
            Contract.Requires(source != null);

            return !source.Any(predicate);
        }

        /// <summary>
        /// Return an IEnumerable&lt;T&gt; where T is of the specified type.
        /// </summary>
        /// <param name="source">The source enumeration.</param>
        /// <param name="type">Type type of T of the resulting IEnumerable&lt;T&gt;.</param>
        /// <returns>An enumeration that can be cast to IEnumerable&lt;T&gt; where T is of the specified type.</returns>
        public static IEnumerable Cast(this IEnumerable source, Type type) {
            var sourceArray = source.Cast<object>().ToArray();
            var destinationArray = Array.CreateInstance(type, sourceArray.Length);
            Array.Copy(sourceArray, destinationArray, sourceArray.Length);
            return destinationArray;
        }

        /// <summary>
        /// Return the second element of a sequence that satisfys a given predicate.
        /// </summary>
        /// <typeparam name="T">The type of the elements in the sequence.</typeparam>
        /// <param name="source">The enumeration to scan for the second matching element.</param>
        /// <returns>Return the second element of a sequence that satisfys a given predicate.</returns>
        public static T Second<T>(this IEnumerable<T> source, Func<T, bool> predicate = null) {
            Contract.Requires(source != null);

            if (predicate == null)
                return source.Skip(1).First();

            return source.SkipWhile(o => !predicate(o)).First(predicate);
        }
    }

    // for files
    public static partial class StringExtensions {

        /// <summary>
        /// Return a copy of this string that ends in '\'. 
        /// Note that no validation is preformed to ensure the string is a valid path.
        /// </summary>
        /// <param name="source">The path to a directory.</param>
        /// <returns>A path endinging in '\'.</returns>
        public static string NormalizeDirectory(this string source) {
            Contract.Requires(source != null);

            if (!source.EndsWith("\\"))
                return source + "\\";

            return source;
        }

        /// <summary>
        /// Make an absolute path RootPath + RelativePath.
        /// </summary>
        /// <param name="relativePath">The relatvie path to make absolute.</param>
        /// <param name="rootPath">An absolute path. If null then the current directory is used.</param>
        /// <returns>The absolute path of the relativePath to the rootPath.</returns>
        public static string MakeFullPath(this string relativePath, string rootPath = null) {
            Contract.Requires(relativePath != null);
            Contract.Requires(rootPath == null || Path.IsPathRooted(rootPath));

            if (rootPath == null)
                rootPath = Environment.CurrentDirectory.NormalizeDirectory();
            var result = new Uri(new Uri(rootPath), (string)relativePath).LocalPath;
            return result;
        }

        /// <summary>
        /// Make an absolute path RootPath + RelativePath and normalize the result as a directory by 
        /// adding a backslash if one is missing.
        /// </summary>
        /// <param name="relativePath">The relatvie path to make absolute.</param>
        /// <param name="rootPath">An absolute path. If null then the current directory is used.</param>
        /// <returns>The absolute path of the relativePath to the rootPath.</returns>
        public static string MakeFullDirectoryPath(this string relativePath, string rootPath = null) {
            Contract.Requires(relativePath != null);
            Contract.Requires(rootPath == null || Path.IsPathRooted(rootPath));

            return MakeFullPath(relativePath.NormalizeDirectory(), rootPath);
        }

        /// <summary>
        /// Copy source file to destination file.
        /// </summary>
        /// <param name="sourceFile">The source file.</param>
        /// <param name="target">The target path. If the path is a directory then the file name will be preserved.</param>
        /// <param name="rootPath">An absolute path to use when resolving source and target paths. If null then the current directory is used.</param>
        /// <param name="createDirectories">If true and the target path does not exist then create the directories.</param>
        /// <param name="overwrite">If true and the target exists then overwrite it.</param>
        public static void CopyFileTo(this string sourceFile, string target,
            string rootPath = null,
            bool createDirectories = true,
            bool overwrite = true) {
            Contract.Requires(sourceFile != null);
            Contract.Requires(target != null);

            sourceFile = sourceFile.MakeFullPath(rootPath);
            target = target.MakeFullPath(rootPath);

            if (target.EndsWith("\\") || Directory.Exists(target))
                target = target.NormalizeDirectory() + Path.GetFileName(sourceFile);

            var targetDir = Path.GetDirectoryName(target);
            if (createDirectories && !Directory.Exists(targetDir))
                Directory.CreateDirectory(targetDir);

            if (overwrite && File.Exists(target))
                File.Delete(target);

            File.Copy(sourceFile, target);
        }
    }

    public static partial class StringExtensions {

        /// <summary>
        /// Returns a copy of this string with the first charcter converted to upper case.
        /// </summary>
        /// <param name="source">The string to copy.</param>
        /// <returns>A copy of the string with the first charcter converted to upper case.</returns>
        public static string ToUpperFirst(this string source) {
            Contract.Requires(source != null);

            if (source.Length == 0)
                return null;

            var first = source[0];
            if (char.IsUpper(first))
                return source;

            return first.ToString().ToUpper() + source.Substring(1);
        }

        /// <summary>
        /// Returns a copy of this string with the first charcter converted to lower case.
        /// </summary>
        /// <param name="source">The string to copy.</param>
        /// <returns>A copy of the string with the first charcter converted to lower case.</returns>
        public static string ToLowerFirst(this string source) {
            Contract.Requires(source != null);

            if (source.Length == 0)
                return null;

            var first = source[0];
            if (char.IsLower(first))
                return source;

            return first.ToString().ToLower() + source.Substring(1);
        }

        /// <summary>
        /// Indicates whether a string is composed of one or more alpha-numeric letters including underbar.
        /// </summary>
        /// <param name="source">The string to check.</param>
        /// <returns>True if the string is composed of one or more alpha-numeric letters including underbar. Otherwise false.</returns>
        public static bool IsIdentifier(this string source) {
            return Regex.IsMatch(source, "[a-zA-Z_]+[a-zA-Z0-9_]*");
        }

        /// <summary>
        /// Returns a string array that contains the substrings in this string that are
        /// delimited by spaces. Empty element are not returned.
        /// </summary>
        /// <param name="source">The string to split.</param>
        /// <returns> 
        /// An enumeration whose elements contain the substrings in this string that are delimited spaceses.
        ///</returns>
        public static IEnumerable<string> StringSplit(this string source) {
            Contract.Requires(source != null);

            return source.StringSplit(" ");
        }

        /// <summary>
        /// Returns a string array that contains the substrings in this string that are
        /// delimited by elements of a specified string array. Empty element are not returned.
        /// </summary>
        /// <param name="source">The string to split.</param>
        /// <param name="delimiters">An list of strings that delimit the substrings in this string.</param>
        /// <returns> 
        /// An enumeration whose elements contain the substrings in this string that are delimited
        /// by one or more strings in separator. 
        ///</returns>
        public static IEnumerable<string> StringSplit(this string source, params string[] delimiters) {
            Contract.Requires(source != null);
            Contract.Requires(delimiters != null);

            return source.StringSplit((IEnumerable<string>)delimiters);
        }

        /// <summary>
        /// Returns a string array that contains the substrings in this string that are
        /// delimited by elements of a specified char array. Empty element are not returned.
        /// </summary>
        /// <param name="source">The string to split.</param>
        /// <param name="delimiters">An list of strings that delimit the substrings in this string.</param>
        /// <returns> 
        /// An enumeration whose elements contain the substrings in this string that are delimited
        /// by one or more chars in separator. 
        ///</returns>
        public static IEnumerable<string> StringSplit(this string source, params char[] delimiters) {
            Contract.Requires(source != null);
            Contract.Requires(delimiters != null);
            
            return source.StringSplit(delimiters.Select(o => o.ToString()));
        }

        /// <summary>
        /// Returns a string array that contains the substrings in this string that are
        /// delimited by elements of a specified string array. Empty element are not returned.
        /// </summary>
        /// <param name="source">The string to split.</param>
        /// <param name="delimiters">An list of strings that delimit the substrings in this string.</param>
        /// <returns> 
        /// An enumeration whose elements contain the substrings in this string that are delimited
        /// by one or more strings in separator. 
        ///</returns>
        public static IEnumerable<string> StringSplit(this string source, IEnumerable<string> delimiters) {
            Contract.Requires(source != null);
            Contract.Requires(delimiters != null);

            return source.Split(delimiters.ToArray(), StringSplitOptions.RemoveEmptyEntries);
        }

        /// <summary>
        /// Replace target string with replacement string but only at the end of the source. Return
        /// source if target doesn't occure at the end.
        /// </summary>
        /// <param name="source">The string on which to do the replacement.</param>
        /// <param name="target">The string to look for at the end of source.</param>
        /// <param name="replacement">The string with witch to replace target if it appears at the end of source.</param>
        /// <returns>Source with target replaced with replacement if it appears at the end of source, else source.</returns>
        public static string ReplaceEnd(this string source, string target, string replacement = "") {
            Contract.Requires(source != null);
            Contract.Requires(target != null);
            Contract.Requires(replacement != null);

            if (!source.EndsWith(target))
                return source;

            return source.Substring(0, source.LastIndexOf(target)) + replacement;
        }

        /// <summary>
        /// Replace target string with replacement string but only at the start of the source. Return
        /// source if target doesn't occure at the start.
        /// </summary>
        /// <param name="source">The string on which to do the replacement.</param>
        /// <param name="target">The string to look for at the start of source.</param>
        /// <param name="replacement">The string with witch to replace target if it appears at the start of source.</param>
        /// <returns>Source with target replaced with replacement if it appears at the start of source, else source.</returns>
        public static string ReplaceStart(this string source, string target, string replacement = "") {
            Contract.Requires(source != null);
            Contract.Requires(target != null);
            Contract.Requires(replacement != null);

            if (!source.StartsWith(target))
                return source;

            return replacement + source.Substring(source.LastIndexOf(target));
        }
    }

    public static partial class UriExtensions {

        /// <summary>
        /// Return a relative url as a relative path. Simply replacing forward slashes with back slashes
        /// it not sufficient because urls and paths have different escaping rules. For example, a file
        /// name with a space in its name will have that space escaped when represented as a url. 
        /// </summary>
        /// <param name="source">A relative url.</param>
        /// <returns>A relative path.</returns>
        public static Uri RelativeUrlToRelativePathUrl(this Uri source) {
            Contract.Ensures(source != null);
            Contract.Ensures(!source.IsAbsoluteUri);

            var url = source.ToString();
            var regex = Regex.Match(url, @"^(?<backup>(\.\./)*)(?<path>.*)$");
            var backup = regex.Get("backup").Replace("/", "\\");
            var path = regex.Get("path");

            var relPath = new Uri(new Uri("http://foo"), path).LocalPath.TrimStart('/');
            var relPathUri = new Uri(backup + relPath, UriKind.RelativeOrAbsolute);
            return relPathUri;
        }
    }

    public static partial class RegexExtensions {

        /// <summary>
        /// Get the value for the named variable in the match. 
        /// </summary>
        /// <param name="match">The result of a regex parse.</param>
        /// <param name="name">The name of the variable from which to get the value.</param>
        /// <returns>The value of the variable.</returns>
        public static string Get(this Match match, string name) {
            Contract.Requires(match != null);
            Contract.Requires(name != null);
            Contract.Requires(match.Success);

            return match.Get<string>(name);
        }

        /// <summary>
        /// Get the value for the named variable in the match. 
        /// </summary>
        /// <param name="match">The result of a regex parse.</param>
        /// <param name="name">The name of the variable from which to get the value.</param>
        /// <returns>The value of the variable deserialized to either a string, int, double, or guid.</returns>
        public static T Get<T>(this Match match, string name) {
            Contract.Requires(match != null);
            Contract.Requires(name != null);
            Contract.Requires(match.Success);
            Contract.Requires(new[] { typeof(string), typeof(int), typeof(double), typeof(Guid) }.Contains(typeof(T)));

            var value = match.Groups[name].Value;

            object result = null;
            if (typeof(T) == typeof(string)) {
                if (!match.Groups[name].Success)
                    return default(T);
                result = value;
            }

            if (typeof(T) == typeof(Guid))
                result = new Guid(value);

            if (typeof(T) == typeof(int))
                result = int.Parse(value);

            if (typeof(T) == typeof(double))
                result = double.Parse(value);

            return (T)result;
        }
    }

    public static partial class TextReaderExtensions {

        /// <summary>
        /// Read a line from the stream and include the newline characters in the string. 
        /// The last string may not end in a new line.
        /// </summary>
        /// <param name="textReader"></param>
        /// <returns>One line read from the stream including newline characters.</returns>
        public static string ReadLineInclusive(this TextReader textReader) {
            Contract.Requires(textReader != null);

            var builder = new StringBuilder();

            var index = 0;
            int value;
            while (index != Environment.NewLine.Length && (value = textReader.Read()) != -1) {

                var character = (char)value;

                if (character != Environment.NewLine[index++])
                    index = 0;

                builder.Append(character);
            }

            return builder.ToString();
        }
    }

    public struct NewLineDetector {
        private int m_count;

        public bool InNewLine {
            get { return m_count > 0 && m_count < Environment.NewLine.Length; }
        }
        public bool AtEndOfNewLine {
            get { return m_count == Environment.NewLine.Length; }
        }
        public bool Next(char character) {
            if (AtEndOfNewLine)
                m_count = 0;

            if (Environment.NewLine[m_count++] != character) {
                m_count = 0;
                return false;
            }

            return AtEndOfNewLine;
        }
    }
}